﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;


namespace ClassLibrary
{
    /* Class for describing popualation */
    public class Population
    {
        public int id { set; get; }
        public string name { set; get; }
        public string path { set; get; }

        public Species species;

        public NaturalSelectionLink pop_natural_selection_link;
        string path_to_natural_selection_link { set; get; }

        public ReproductionLink pop_reproduction_link;
        string path_to_reproduction_link { set; get; }


        // program structures for handling current and new generations of individuals
        public int amount_individuals { set; get; }
        public List<Individual> massive_individuals;
        public int new_amount_individuals { set; get; }
        public List<Individual> new_generation_massive_individuals;

        // statistic counters
        public int statistics_steps_amount;
        public List<PopulationStatistics> population_statistic_list = new List<PopulationStatistics>();

        /* Constructor */
        public Population(string path_to_population, Species sp)
        {
            path = path_to_population;
            species = sp;

            massive_individuals = new List<Individual>();

            // open XML file
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(System.IO.File.ReadAllText(path_to_population));

            // reading attributes of the first node
            name = doc.DocumentElement.GetAttribute("Name");
            amount_individuals = Convert.ToInt32(doc.DocumentElement.GetAttribute("AmountIndividuals"));

            // reading all data about individuals
            if (amount_individuals != 0)
            {
                // reading "individuals" node                
                foreach (XmlNode x in doc.GetElementsByTagName("individual"))
                {
                    massive_individuals.Add(new Individual(x.Attributes[0].Value, this));
                }                
            }            
        }


        /* 
         * Method 
         * Args: no
         * Does: initialization
        */
        public void initPopulation()
        {
            foreach (Individual individual in massive_individuals)
            {
                individual.initIndividual();
            }
        }


        /*
         * Method 
         * Args: no
         * Does: connects natural selection link to population according to
         * natural selecton parameters (amount of individual, time step = statistics_step)   
        */
        public string getPathToNaturalSelectionLink()
        {
            NaturalSelectorParametersArea na_se_pa_area = new NaturalSelectorParametersArea( statistics_steps_amount, statistics_steps_amount,
                                                          amount_individuals, amount_individuals);
            path_to_natural_selection_link = species.sp_natural_selection_table.getPathToNaturalSelectionLink(na_se_pa_area);
            return path_to_natural_selection_link;
        }


        /*
         * Method 
         * Args: no
         * Does: connects reproduction link to population according to
         * natural selecton parameters (amount of individual, time step = statistics_step)   
        */
        public string getPathToReproductionLink()
        {
            NaturalSelectorParametersArea na_se_pa_area = new NaturalSelectorParametersArea(statistics_steps_amount, statistics_steps_amount,
                                                          amount_individuals, amount_individuals);
            path_to_reproduction_link = species.sp_natural_selection_table.getPathToReproductionLink(na_se_pa_area);
            return path_to_reproduction_link;
        }


        /*
         * Method 
         * Args: no
         * Does: sets path to natural selection link
        */
        public void setNaturalSelectionLink( NaturalSelectionLink na_se_link)
        {
            pop_natural_selection_link = na_se_link;
        }


        /*
         * Method 
         * Args: no
         * Does: sets path to reproduction link
        */
        public void setReproductionLink( ReproductionLink re_link)
        {
            pop_reproduction_link = re_link;
        }


        /* 
         * Method 
         * Args: no
         * Does: countins phenotype characteristics
        */
        public void popCountPhenotypeCharacteristics()
        {
            foreach (Individual individual in massive_individuals)
            {
                individual.countPhenotypeCharacteristics();
            }
        }


        /* 
         * Method 
         * Args: no
         * Does: gets events for all individuals in population
        */
        public void popGetEvents()
        {
            foreach (Individual individual in massive_individuals)
            {
                individual.getEvent();
            }
        }


        /* 
         * Method 
         * Args: no
         * Does: gets events for all individuals in population
        */
        public void popDoNextModellingStep()
        {
            // processing events for individuals in population
            processEvents();

            // deleting old generation and connecting new one to population
            replaceOldGeneration();
        }


        /*
         * Method
         * Args: no
         * Does: process all events for individuals in population
        */
        public void processEvents()
        {
            foreach (int event_id in EventTable.int_event_table.Keys)
            {
                // event being handled
                Event even = EventTable.getEvent(event_id);

                // list of individuals with this event
                List<Individual> individual_list = makeInvidiualList(even);

                // handle event for individuals from list
                sendIndividualListToEventHandler(even, individual_list);
            }
        }


        /* 
         * Method 
         * Args: event
         * Does: makes a list of individuals with specific event
        */
        public List<Individual> makeInvidiualList(Event even)
        {
            List<Individual> individual_list = new List<Individual>();
            foreach (Individual individual in massive_individuals)
            {
                if (individual.individual_event == even)
                {
                    individual_list.Add(individual);
                }
            }
            return individual_list;
        }


        /*
         * Method
         * Args: no
         * Does: replace old generation by new one
        */
        public void replaceOldGeneration()
        {
            amount_individuals = new_amount_individuals;
            massive_individuals = new_generation_massive_individuals;
        }


        /*
         * Method
         * Args: event, individual list
         * Does: sends list of individuals to handler responsible 
         *       for specific event
        */
        public void sendIndividualListToEventHandler( Event even, List<Individual> individual_list)
        {           

            // event #0
            if (even == EventTable.int_event_table[0])
            {
                handelEventNumber0(individual_list);
            }

            // event #1
            if (even == EventTable.int_event_table[0])
            {
                handelEventNumber1(individual_list);
            }
        }


        /*
         * Method EventHandler #0
         * Args: individual list
         * Does: handles event #0,
         *       which is responsible for having prognecy
        */
        public void handelEventNumber0(List<Individual> individual_list)
        {
            // counter for individuals in new generation
            int individuals_in_new_generation = 0;

            // list of individuals in new generation
            List<Individual> new_generation_individual_list = new List<Individual>();

            // pares of individuals in new generation
            int pares_amount = individual_list.Count / 2;            

            // getting children for each pare of individuals
            for (int i = 0; i < pares_amount; i++)
            {
                int first_parent_id = 2 * i;
                int second_parent_id = 2 * i + 1;
                int children_amount = determineChildrenNumber(individual_list[first_parent_id], individual_list[second_parent_id]);

                // getting children
                for (int j = 0; j < children_amount; j++)
                {
                    int chromosome_pares_amount = species.sp_chromosome_table.chromosome_pairs_amount;
                    Dictionary<string, Chromosome[]> child_chromosomes = new Dictionary<string, Chromosome[]>();

                    // getting child chromosomes from parents
                    Chromosome[] first_parent_child_chromosomes = individual_list[first_parent_id].generateChildChromosomeSet();
                    Chromosome[] second_parent_child_chromosomes = individual_list[second_parent_id].generateChildChromosomeSet();

                    int child_id = individuals_in_new_generation;
                    string child_name = "default individuals's name";  
                    for (int k = 0; k < chromosome_pares_amount; k++)
                    {
                        child_chromosomes.Add(k.ToString(), new Chromosome[2]);
                        child_chromosomes[k.ToString()][0] = first_parent_child_chromosomes[k];
                        child_chromosomes[k.ToString()][1] = second_parent_child_chromosomes[k];
                    }

                    // creating child
                    Individual child_individual = new Individual(child_id, child_name, this, child_chromosomes);
                    new_generation_individual_list.Add(child_individual);

                    // incrementing counter
                    individuals_in_new_generation++;
                }
            }

            new_amount_individuals = individuals_in_new_generation;
            new_generation_massive_individuals = new_generation_individual_list;
        }


        /*
         * Method EventHandler #1
         * Args: individual list
         * Does: handles event #1
         *       which is responsible for no having prognecy  
        */
        public void handelEventNumber1(List<Individual> individual_list)
        {

        }


       /*
        * Method
        * Args: pare of individuals
        * Does: determines the number of children for given pare
        * Returns: children number       
       */
        public int determineChildrenNumber( Individual individual1, Individual individual2)
        {
            int[] characteristic_vector1 = individual1.getCharacteristicVector();
            int[] characteristic_vector2 = individual2.getCharacteristicVector();

            IntMassive int_massive1 = new IntMassive();
            IntMassive int_massive2 = new IntMassive();

            int_massive1.massive = characteristic_vector1;
            int_massive2.massive = characteristic_vector2;

            int children_amount1 = pop_reproduction_link.reproduction[int_massive1];
            int children_amount2 = pop_reproduction_link.reproduction[int_massive2];

            return (children_amount1 + children_amount2) / 2;
        }


        /*
        * Method
        * Args: no
        * Does: gathers all statistics for population in one step
       */
        public void popGatherOneStepStatistics()
        {
            PopulationStatistics population_statistics = new PopulationStatistics(this);

            statistics_steps_amount++;
            population_statistic_list.Add(population_statistics);
        }

    }


    /*
     * Class for counting statistics
     * for one generation
    */ 
    public class PopulationStatistics
    {
         public int number_of_individuals { set; get; }

         public Dictionary<int, UInt64[]> numbers_of_gene_alleles { set; get; }
         public Dictionary<int, double[]> gene_alleles_frequencies { set; get; }

        public double[] phenotype_characteristic_medium_values { set; get; }

        public int[] numbers_of_events { set; get; }
        public double[] event_frequencies { set; get; }

        Population population;

        /* constructor */
        public PopulationStatistics(Population pop)
        {
            population = pop;

            // preparing table for frequencies of gene allels
            {
                GeneTable gene_table = population.species.sp_gene_table;
                int gene_types_amount = gene_table.gene_types_amount;
                numbers_of_gene_alleles = new Dictionary<int,UInt64[]>();
                gene_alleles_frequencies = new Dictionary<int,double[]>();

                // counting for different gene types
                foreach (GeneType g in gene_table.gene_table)
                {
                    numbers_of_gene_alleles.Add(g.allele_type_id, new UInt64[g.amount_alelles]);
                    gene_alleles_frequencies.Add(g.allele_type_id, new double[g.amount_alelles]);
                }
            }

            phenotype_characteristic_medium_values = new double[population.species.sp_phenotype_table.characteristics_amount];

            numbers_of_events = new int[EventTable.event_amount];
            event_frequencies = new double[EventTable.event_amount];

            countStatistics();
        }


        /*
         * Method 
         * Agrs: no
         * Does: counts all statistics
        */
        public void countStatistics()
        {
            // number of individuals
            number_of_individuals = population.amount_individuals;

            // gene frequencies
            countGeneFrequencies();

            // characteristic medium values
            countMediumPhenotypeCharacteristics();

            // event_numbers
            countEventFrequencies();
        }


        /*
         * Method 
         * Args: no
         * Does: counts gene frequencies
        */ 
        public void countGeneFrequencies()
        {
            Dictionary<int, UInt64[]> individual_numbers_of_alleles;

            if (population.amount_individuals != 0)
            {
                individual_numbers_of_alleles = population.massive_individuals.First().createMassForCountingGeneAllels();
            } else
            {
                return;
            }
            
            // summurizing alleles from each individual
            foreach ( Individual individual in population.massive_individuals)
            {
                individual.countGeneAllels(individual_numbers_of_alleles);
                Supplimentary.addUInt64UInt64TwoDimensionalToFirstMassiveSecondOne(numbers_of_gene_alleles,individual_numbers_of_alleles);
            }

            // getting frequencies for each individual             
            foreach (int key in numbers_of_gene_alleles.Keys)
            {            
                UInt64[] number_allels = numbers_of_gene_alleles[key];
                double allels_amount = Supplimentary.sumUInt64Massive(number_allels);                
                for (int j = 0; j < number_allels.Count(); j++)
                {
                    gene_alleles_frequencies[key][j] = number_allels[j] / allels_amount;
                }
            }
        }


        /*
         * Method
         * Args: no
         * Does: counts medium values for phenotype characteristics in population
        */
        public void countMediumPhenotypeCharacteristics()
        {
            if (population.amount_individuals == 0)
            {
                return;
            }

            foreach (Individual individual in population.massive_individuals)
            {
                Supplimentary.addDoubleIntOneDimensionalToFirstMassiveSecondOne(phenotype_characteristic_medium_values, individual.getMassiveCharacteristicValues());
            }

            for (int i = 0; i < phenotype_characteristic_medium_values.Count(); i++)
            {
                phenotype_characteristic_medium_values[i] /= population.amount_individuals;
            }
        }


        /*
         * Method
         * Args: no
         * Does: counts numbers of event for individuals in population
        */
        public void countEventFrequencies()
        {
            if (population.amount_individuals == 0)
            {
                return;
            }

            foreach (Individual individual in population.massive_individuals)
            {
                int event_id = individual.individual_event.event_id;
                numbers_of_events[event_id]++;
            }

            double event_amount = Supplimentary.sumIntMassive(numbers_of_events);
            for (int i = 0; i < numbers_of_events.Count(); i++)
            {
                event_frequencies[i] = numbers_of_events[i] / event_amount;
            }
        }
    }
}